package wiki.hitime.stl.daq.manager;

import com.alibaba.fastjson.JSON;
import wiki.hitime.stl.daq.bean.db.DaqExecuteRecord;
import wiki.hitime.stl.daq.bean.db.DaqExecuteRule;
import wiki.hitime.stl.daq.bean.db.DaqExecuteRuleHistory;
import wiki.hitime.stl.daq.bean.dto.manage.ExecuteRuleDTO;
import wiki.hitime.stl.daq.bean.dto.manage.ExecuteRuleViewDTO;
import wiki.hitime.stl.daq.bean.dto.req.ExecuteRuleGetReqDTO;
import wiki.hitime.stl.daq.bean.dto.req.ExecuteRuleUpdateActionReqDTO;
import wiki.hitime.stl.daq.bean.dto.req.ExecuteRuleUpdateReqDTO;
import wiki.hitime.stl.daq.bean.dto.req.ExecuteRuleViewListReqDTO;
import wiki.hitime.stl.daq.enums.ExecuteActionEnum;
import wiki.hitime.stl.daq.enums.OperateActionEnum;
import wiki.hitime.stl.daq.enums.ProcessStatusEnum;
import wiki.hitime.stl.daq.exception.ServiceException;
import wiki.hitime.stl.daq.mapper.DaqExecuteRuleHistoryMapper;
import wiki.hitime.stl.daq.mapper.DaqExecuteRuleMapper;
import wiki.hitime.stl.daq.util.CronUtils;
import wiki.hitime.stl.daq.util.UserContextUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author zhangdaoping
 * @create 2019-07-15 17:31
 */
@Service
public class DaqExecuteRuleManager {

    private DaqExecuteRuleMapper daqExecuteRuleMapper;
    private DaqExecuteRuleHistoryMapper daqExecuteRuleHistoryMapper;
    private DaqExecuteRecordManager daqExecuteRecordManager;

    public DaqExecuteRuleManager(DaqExecuteRuleMapper daqExecuteRuleMapper,
                                 DaqExecuteRuleHistoryMapper daqExecuteRuleHistoryMapper,
                                 DaqExecuteRecordManager daqExecuteRecordManager) {
        this.daqExecuteRuleMapper = daqExecuteRuleMapper;
        this.daqExecuteRuleHistoryMapper = daqExecuteRuleHistoryMapper;
        this.daqExecuteRecordManager = daqExecuteRecordManager;
    }

    /**
     * 加载待执行的任务
     *
     * @return
     */
    public List<DaqExecuteRule> loadPendingTask() {
        Example loadPendingExample = new Example(DaqExecuteRule.class, true, true);
        loadPendingExample.createCriteria().andCondition("next_execute_time<=unix_timestamp()*1000")
                .andEqualTo("executeAction", ExecuteActionEnum.ENABLED.getStatus())
                .andEqualTo("isDelete", Boolean.FALSE);
        return daqExecuteRuleMapper.selectByExample(loadPendingExample);
    }

    /**
     * 锁定规则
     *
     * @param ruleId 规则id
     * @return
     */
    public boolean lockTask(Long ruleId) {
        DaqExecuteRule updateRule = DaqExecuteRule.builder()
                .executeAction(ExecuteActionEnum.EXECUTING.getStatus())
                .build();
        Example lockTaskExample = new Example(DaqExecuteRule.class, true, true);
        lockTaskExample.createCriteria().andEqualTo("id", ruleId)
                .andEqualTo("executeAction", ExecuteActionEnum.ENABLED.getStatus());
        return daqExecuteRuleMapper.updateByExampleSelective(updateRule, lockTaskExample) == 1;
    }

    /**
     * 解除锁定规则
     *
     * @param ruleId 规则id
     * @return
     */
    public boolean unlockTask(Long ruleId) {
        DaqExecuteRule updateRule = DaqExecuteRule.builder()
                .executeAction(ExecuteActionEnum.ENABLED.getStatus())
                .build();
        Example lockTaskExample = new Example(DaqExecuteRule.class, true, true);
        lockTaskExample.createCriteria().andEqualTo("id", ruleId)
                .andEqualTo("executeAction", ExecuteActionEnum.EXECUTING.getStatus());
        return daqExecuteRuleMapper.updateByExampleSelective(updateRule, lockTaskExample) == 0;
    }

    /**
     * 更新下次执行时间
     *
     * @param ruleId          规则id
     * @param lastExecuteTime 上次执行时间
     * @param nextExecuteTime 下次执行时间
     */
    public void updateNextExecuteTime(Long ruleId, long lastExecuteTime, long nextExecuteTime) {
        DaqExecuteRule updateRule = DaqExecuteRule.builder()
                .lastExecuteTime(lastExecuteTime)
                .nextExecuteTime(nextExecuteTime)
                .build();
        Example selectExample = new Example(DaqExecuteRule.class, true, true);
        selectExample.createCriteria().andEqualTo("id", ruleId);
        daqExecuteRuleMapper.updateByExampleSelective(updateRule, selectExample);
    }

    /**
     * 添加规则
     *
     * @param executeRuleDTO 规则DTO
     * @return
     */
    public Long add(ExecuteRuleDTO executeRuleDTO) throws ServiceException {
        DateTime nowDate = DateTime.now();
        DaqExecuteRule daqExecuteRule = DaqExecuteRule.builder()
                .ruleName(executeRuleDTO.getRuleName())
                .cronExpression(executeRuleDTO.getCronExpression())
                .lastExecuteTime(nowDate.getMillis())
                .nextExecuteTime(CronUtils.parserNextFiredTime(executeRuleDTO.getCronExpression(), nowDate.toDate()))
                .executeAction(ExecuteActionEnum.ENABLED.getStatus())
                .remark(executeRuleDTO.getRemark())
                .ctlVersion(1L)
                .createdUserId(UserContextUtil.getJobNumber())
                .createdUser(UserContextUtil.getTrueName())
                .modifiedUserId(UserContextUtil.getJobNumber())
                .modifiedUser(UserContextUtil.getTrueName())
                .build();
        daqExecuteRuleMapper.insertSelective(daqExecuteRule);
        Long ruleId = daqExecuteRule.getId();
        this.addHistory(daqExecuteRule.getId());
        return ruleId;
    }

    private void addHistory(Long ruleId) {
        DaqExecuteRule daqExecuteRule = daqExecuteRuleMapper.selectByPrimaryKey(ruleId);
        DaqExecuteRuleHistory daqExecuteRuleHistory = new DaqExecuteRuleHistory();
        BeanUtils.copyProperties(daqExecuteRule, daqExecuteRuleHistory);
        daqExecuteRuleHistory.setRuleId(daqExecuteRule.getId());
        OperateActionEnum operateActionEnum = daqExecuteRule.getCtlVersion() == 1 ? OperateActionEnum.ADD : OperateActionEnum.UPDATE;
        operateActionEnum = Boolean.TRUE.equals(daqExecuteRule.getIsDelete()) ? OperateActionEnum.DELETE : operateActionEnum;
        daqExecuteRuleHistory.setOperateAction(operateActionEnum.getStatus());
        daqExecuteRuleHistory.setOperateData(JSON.toJSONString(daqExecuteRule));
        daqExecuteRuleHistoryMapper.insertSelective(daqExecuteRuleHistory);
    }

    /**
     * 获取规则视图实体集合
     *
     * @param reqDTO
     * @return
     */
    public PageInfo listRuleView(ExecuteRuleViewListReqDTO reqDTO) {
        Example executeSelectExample = new Example(DaqExecuteRule.class, true, false);
        executeSelectExample.createCriteria().andEqualTo("executeAction", reqDTO.getExecuteAction())
                .andEqualTo("isDelete", Boolean.FALSE);
        Example.Criteria like = executeSelectExample.createCriteria();
        if (!StringUtils.isEmpty(reqDTO.getExecuteRuleName())) {
            like.andLike("ruleName", "%" + reqDTO.getExecuteRuleName() + "%");
        }
        executeSelectExample.and(like);
        PageHelper.startPage(reqDTO.getPageInfo());
        List<DaqExecuteRule> executeRulePage = daqExecuteRuleMapper.selectByExample(executeSelectExample);
        Page<DaqExecuteRule> page = (Page<DaqExecuteRule>) executeRulePage;
        List<DaqExecuteRule> daqExecuteRuleList = page.getResult();
        List<Long> ruleIdList = daqExecuteRuleList.stream().map(DaqExecuteRule::getId).collect(Collectors.toList());
        List<DaqExecuteRecord> daqExecuteRecordList = daqExecuteRecordManager.listMax(ruleIdList);
        List<ExecuteRuleViewDTO> executeRuleViewDTOList = daqExecuteRuleList.stream().map(r -> convertDTO(r, daqExecuteRecordList)).collect(Collectors.toList());
        PageInfo pageInfo = new PageInfo(executeRulePage);
        pageInfo.setList(executeRuleViewDTOList);
        return pageInfo;
    }

    private ExecuteRuleViewDTO convertDTO(DaqExecuteRule daqExecuteRule, List<DaqExecuteRecord> daqExecuteRecordList) {
        ExecuteRuleViewDTO executeRuleViewDTO = new ExecuteRuleViewDTO();
        BeanUtils.copyProperties(daqExecuteRule, executeRuleViewDTO);
        executeRuleViewDTO.setCronExpressionName(CronUtils.parserDescription(daqExecuteRule.getCronExpression(), DateTime.now().toDate()));
        ExecuteActionEnum executeActionEnum = ExecuteActionEnum.valueOf(daqExecuteRule.getExecuteAction());
        Assert.notNull(executeActionEnum, "执行状态为空");
        executeRuleViewDTO.setExecuteActionName(executeActionEnum.getMsg());
        executeRuleViewDTO.setLastExecuteDate(new DateTime(daqExecuteRule.getLastExecuteTime()).toDate());
        executeRuleViewDTO.setNextExecuteDate(new DateTime(daqExecuteRule.getNextExecuteTime()).toDate());
        executeRuleViewDTO.setNextExecuteDate(new DateTime(daqExecuteRule.getNextExecuteTime()).toDate());

        // 加载执行记录信息
        Optional<DaqExecuteRecord> daqExecuteRecordResult = daqExecuteRecordList.stream().filter(r -> r.getExecuteRuleId().equals(daqExecuteRule.getId())).findFirst();
        if (daqExecuteRecordResult.isPresent()) {
            DaqExecuteRecord daqExecuteRecord = daqExecuteRecordResult.get();
            int executeSeconds = Seconds.secondsBetween(new DateTime(daqExecuteRecord.getStartTime()), new DateTime(daqExecuteRecord.getEndTime())).getSeconds();
            executeRuleViewDTO.setExecuteSeconds(executeSeconds);
            executeRuleViewDTO.setProcessStatus(daqExecuteRecord.getProcessStatus());
            ProcessStatusEnum processStatusEnum = ProcessStatusEnum.valueOf(daqExecuteRecord.getProcessStatus());
            Assert.notNull(processStatusEnum, "处理状态为空");
            executeRuleViewDTO.setProcessStatusName(processStatusEnum.getMsg());
            executeRuleViewDTO.setProcessMsg(daqExecuteRecord.getProcessMsg());
        }
        return executeRuleViewDTO;
    }

    /**
     * 获取db对象
     *
     * @param executeRuleId 编号
     * @return
     */
    public DaqExecuteRule get(Long executeRuleId) {
        Example selectExample = new Example(DaqExecuteRule.class, true, true);
        selectExample.createCriteria().andEqualTo("id", executeRuleId).andEqualTo("isDelete", Boolean.FALSE);
        return daqExecuteRuleMapper.selectOneByExample(selectExample);
    }

    /**
     * 获取DTO
     *
     * @param reqDTO 请求实体
     * @return
     */
    public ExecuteRuleDTO getDTO(ExecuteRuleGetReqDTO reqDTO) {
        Example selectExample = new Example(DaqExecuteRule.class, true, true);
        selectExample.createCriteria().andEqualTo("id", reqDTO.getId()).andEqualTo("isDelete", Boolean.FALSE);
        DaqExecuteRule daqExecuteRule = daqExecuteRuleMapper.selectOneByExample(selectExample);
        return convertDTO(daqExecuteRule);
    }

    private ExecuteRuleDTO convertDTO(DaqExecuteRule daqExecuteRule) {
        if (daqExecuteRule == null) {
            return null;
        }
        ExecuteRuleDTO executeRuleDTO = new ExecuteRuleDTO();
        BeanUtils.copyProperties(daqExecuteRule, executeRuleDTO);
        return executeRuleDTO;
    }

    /**
     * 修改规则
     *
     * @param reqDTO 请求实体
     * @return 修改结果，true-成功，false-失败
     */
    public Boolean updateRule(ExecuteRuleUpdateReqDTO reqDTO) throws ServiceException {
        return updateRule(reqDTO.getExecuteRuleDTO());
    }

    /**
     * 修改规则
     *
     * @param executeRuleDTO 请求实体
     * @return 修改结果，true-成功，false-失败
     */
    public Boolean updateRule(ExecuteRuleDTO executeRuleDTO) throws ServiceException {
        DaqExecuteRule daqExecuteRule = daqExecuteRuleMapper.selectByPrimaryKey(executeRuleDTO.getId());
        if (daqExecuteRule == null) {
            throw new ServiceException("未查到待修改的规则");
        }
        BeanUtils.copyProperties(executeRuleDTO, daqExecuteRule);
        return this.updateRule(daqExecuteRule);
    }

    /**
     * 修改规则
     *
     * @param daqExecuteRule 执行
     * @return 修改结果，true-成功，false-失败
     */
    public Boolean updateRule(DaqExecuteRule daqExecuteRule) {
        daqExecuteRule.setCtlVersion(daqExecuteRule.getCtlVersion() + 1);
        daqExecuteRule.setModifiedUserId(UserContextUtil.getJobNumber());
        daqExecuteRule.setModifiedUser(UserContextUtil.getTrueName());
        Example updateExample = new Example(DaqExecuteRule.class, true, true);
        updateExample.createCriteria().andEqualTo("id", daqExecuteRule.getId()).andEqualTo("ctlVersion", daqExecuteRule.getCtlVersion() - 1);
        int count = daqExecuteRuleMapper.updateByExampleSelective(daqExecuteRule, updateExample);
        if (count == 1) {
            this.addHistory(daqExecuteRule.getId());
        }
        return count == 1;
    }

    /**
     * 修改执行状态
     *
     * @param reqDTO
     * @return
     */
    public Boolean updateExecuteAction(ExecuteRuleUpdateActionReqDTO reqDTO) {
        DaqExecuteRule daqExecuteRule = daqExecuteRuleMapper.selectByPrimaryKey(reqDTO.getExecuteRuleId());
        daqExecuteRule.setExecuteAction(reqDTO.getExecuteAction());
        return this.updateRule(daqExecuteRule);
    }

    /**
     * 逻辑删除规则
     *
     * @param executeRuleId
     */
    public void delete(Long executeRuleId) {
        Example selectExample = new Example(DaqExecuteRule.class, true, true);
        selectExample.createCriteria().andEqualTo("id", executeRuleId).andEqualTo("isDelete", Boolean.FALSE);
        DaqExecuteRule rule = daqExecuteRuleMapper.selectOneByExample(selectExample);
        Assert.notNull(rule, "执行规则为空");
        rule.setIsDelete(Boolean.TRUE);
        this.updateRule(rule);
    }
}
